/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package pb

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"sync"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/common/util"
	"hundsun.com/hsl/hschain/contract/native/token/types"
	"hundsun.com/hsl/hschain/protos"
	pbconfig "hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/protos/ledger"
)

var once sync.Once

var genesisBlock *ledger.Block
var genesisTxPayload *pbconfig.GenesisTransactionPayload

const configKeyGenesisBlockPath = "chain.genesisBlockPath"
const (
	// MinCheckBlockDistance 区块校验最小间隔
	MinCheckBlockDistance = 1
	// MaxCheckBlockDistance 区块校验最大间隔
	MaxCheckBlockDistance = 32
)

// GetGenesisBlock 获取创世纪块
func GetGenesisBlock() *ledger.Block {
	once.Do(func() {
		genesisBlockPath := config.GetStringConfigValue(configKeyGenesisBlockPath, "")
		if genesisBlockPath == "" {
			panic("chain.genesisBlockPath not set")
		}
		if !util.FileExists(genesisBlockPath) {
			panic("genesis block '" + genesisBlockPath + "' not exists")
		}
		genesisBlock, genesisTxPayload = ParseGenesisBlock(genesisBlockPath)
		log.Println("------------------genesis block------------------")
		log.Println("ChainID: ", genesisTxPayload.ChainID)
		log.Println("GenesisBlockHash: ", hex.EncodeToString(genesisBlock.Hash()))
		log.Println("GenesisBlockTime: ", genesisBlock.Header.BlockTime)
		log.Println("Reward: ", genesisTxPayload.Reward)
		log.Println("GasLimit: ", genesisTxPayload.GasLimit)
		log.Println("Crypto.SymmetricAlgorithm: ", genesisTxPayload.Crypto.SymmetricAlgorithm)
		log.Println("Crypto.AsymmetricAlgorithm: ", genesisTxPayload.Crypto.AsymmetricAlgorithm)
		log.Println("Crypto.HashFamily: ", genesisTxPayload.Crypto.HashFamily)
		log.Println("CheckBlock.Enabled: ", genesisTxPayload.CheckBlock.Enabled)
		log.Println("CheckBlock.Distance: ", genesisTxPayload.CheckBlock.Distance)
		if genesisTxPayload.SequenceCenter != nil {
			log.Println("SequenceCenter.PeerID: ", genesisTxPayload.SequenceCenter.PeerID)
			log.Println("SequenceCenter.SignerAddress: ", genesisTxPayload.SequenceCenter.SignerAddress)
		}
		log.Println("genesisTxPayload.Consensus.Type: ", genesisTxPayload.Consensus.Type)
		log.Println("BlockLimit.MaxTxCount: ", genesisTxPayload.BlockLimit.MaxTxCount)
		log.Println("BlockLimit.MaxTxSizeBytes: ", genesisTxPayload.BlockLimit.MaxTxSizeBytes)
		log.Println("BlockLimit.MaxBlockSizeBytes: ", genesisTxPayload.BlockLimit.MaxBlockSizeBytes)
		log.Println("TimeLimit.MaxTxTTL: ", genesisTxPayload.TimeLimit.MaxTxTTL)
		log.Println("TimeLimit.MaxTimeIntervalError: ", genesisTxPayload.TimeLimit.MaxTimeIntervalError)
		if genesisTxPayload.ParaChainConfig != nil {
			log.Println("ParaChainConfig.MaxChainCount: ", genesisTxPayload.ParaChainConfig.MaxChainCount)
			log.Println("ParaChainConfig.MaxValidatorCountPerChain: ", genesisTxPayload.ParaChainConfig.MaxValidatorCountPerChain)
			log.Println("ParaChainConfig.ValidatorDeposit: ", genesisTxPayload.ParaChainConfig.ValidatorDeposit)
		}
		if genesisTxPayload.Manager != nil {
			log.Println("ParaChainConfig.Manager lenth: ", len(genesisTxPayload.Manager.Addrs))
		}
		if genesisTxPayload.FeeLimit != nil {
			log.Println("FeeLimit.MinTxFee: ", genesisTxPayload.FeeLimit.MinTxFee)
		}
		log.Println("------------------genesis block------------------")
	})
	return genesisBlock
}

// ParseGenesisBlock 解析创世块
func ParseGenesisBlock(genesisBlockPath string) (*ledger.Block, *pbconfig.GenesisTransactionPayload) {
	genesisBlockBytes, err := ioutil.ReadFile(genesisBlockPath)
	if err != nil {
		panic(err)
	}
	var uncheckedGenesisBlock ledger.Block
	err = proto.Unmarshal(genesisBlockBytes, &uncheckedGenesisBlock)
	if err != nil {
		panic(err)
	}
	err = CheckGenesisBlock(&uncheckedGenesisBlock)
	if err != nil {
		panic(err)
	}
	genesisBlock = &uncheckedGenesisBlock
	genesisTxPayload, err = ParseGenesisTransactionPayload(genesisBlock.Transactions[0].Payload)
	if err != nil {
		panic(err)
	}
	return genesisBlock, genesisTxPayload
}

// GetGenesisTransactionPayload 获取创世纪块中交易的payload
func GetGenesisTransactionPayload() *pbconfig.GenesisTransactionPayload {
	//触发初始化
	_ = GetGenesisBlock()
	return genesisTxPayload
}

// GetCheckBlockNumber 获取验证块块号
func GetCheckBlockNumber(blockNumber uint64) (checkBlockNumber uint64, checkBlockEnabled bool) {
	genesisTxPayload := GetGenesisTransactionPayload()
	if genesisTxPayload.CheckBlock.Enabled {
		checkBlockNumber = 0
		if blockNumber > genesisTxPayload.CheckBlock.Distance {
			checkBlockNumber = blockNumber - genesisTxPayload.CheckBlock.Distance
		}
		return checkBlockNumber, true
	}
	return 0, false
}

// CheckGenesisBlock 验证创世块
func CheckGenesisBlock(block *ledger.Block) error {
	if block == nil {
		return errors.New("nil block")
	}
	//先校验交易触发crypto初始化
	err := checkGenesisBlockTransactions(block.Transactions, block.Header)
	if err != nil {
		return err
	}
	return checkGenesisBlockHeader(block.Header)
}

func checkGenesisBlockTransactions(txs []*ledger.Transaction, blkHeader *ledger.BlockHeader) error {
	if len(txs) != 1 {
		return errors.New("txs count of genesis block must be one")
	}
	tx := txs[0]
	if tx.Ty != types.NativeToken {
		return errors.New("ty of transaction in genesis block must be nativetoken")
	}
	if tx.From != string(protos.GenesisCoinbase) {
		return errors.New("from of transaction in genesis block must be " + string(protos.GenesisCoinbase))
	}
	if tx.To != "" {
		return errors.New("to of transaction in genesis block must be empty")
	}
	if tx.ContractId != nil {
		return errors.New("contractId of transaction in genesis block must be nil")
	}
	if tx.Value != 0 {
		return errors.New("value of transaction in genesis block must be 0")
	}
	if tx.Fee != 0 {
		return errors.New("fee of transaction in genesis block must be 0")
	}
	if tx.TTL != 0 {
		return errors.New("TTL of transaction in genesis block must be 0")
	}
	if tx.CreateTime != blkHeader.BlockTime {
		return errors.New("create of transaction in genesis block must be equal to blockTime")
	}
	if tx.Nonce != 0 {
		return errors.New("nonce of transaction in genesis block must be 0")
	}
	if tx.Signature != nil {
		return errors.New("signature of transaction in genesis block must be nil")
	}

	payload, err := ParseGenesisTransactionPayload(tx.Payload)
	if err != nil {
		return err
	}
	return payload.Validate()
}

//ParseGenesisTransactionPayload 解析创世块中交易payload
func ParseGenesisTransactionPayload(txPayloadBytes []byte) (*pbconfig.GenesisTransactionPayload, error) {
	if len(txPayloadBytes) == 0 {
		return nil, errors.New("payload of transaction in genesis block cannot be empty")
	}
	var payload pbconfig.GenesisTransactionPayload
	err := proto.Unmarshal(txPayloadBytes, &payload)
	if err != nil {
		return nil, err
	}
	if !payload.GetCheckBlock().Enabled {
		return nil, errors.New("value of 'enable' field in 'checkBlock' section must be true")
	}

	if payload.GetCheckBlock().GetDistance() < MinCheckBlockDistance ||
		payload.GetCheckBlock().GetDistance() > MaxCheckBlockDistance {
		return nil, errors.New("value of 'distance' field of 'checkBlock' section should be integer and in range [1,32]")
	}

	return &payload, nil
}

func checkGenesisBlockHeader(blkHeader *ledger.BlockHeader) error {
	if blkHeader == nil {
		return errors.New("nil block header")
	}
	var err error
	hashSize, err := crypto.HashSize()
	if err != nil {
		return err
	}
	if len(blkHeader.ParentHash) != hashSize {
		return fmt.Errorf("size of parentHash of genesis block must be %d", hashSize)
	}
	if len(blkHeader.Consensus) != 0 {
		return errors.New("consensus of genesis block must be empty")
	}
	if blkHeader.BlockTime < 0 {
		return errors.New("blockTime of genesis block cannot be negative")
	}
	if blkHeader.BlockSequence != 0 {
		return errors.New("blockSequence of genesis block must be zero")
	}
	if blkHeader.Number != 0 {
		return errors.New("number of genesis block must be zero")
	}
	if blkHeader.Signature != nil {
		return errors.New("signature of genesis block must be nil")
	}
	if blkHeader.BlockSequenceSignature != nil {
		return errors.New("blockSequenceSignature of genesis block must be nil")
	}
	if blkHeader.CheckBlock != 0 {
		return errors.New("checkBlock of genesis block must be zero")
	}
	if len(blkHeader.StateHashOfCheckBlock) != 0 {
		return errors.New("stateHashOfCheckBlock of genesis block must be empty")
	}
	return nil
}
